#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Purpose: Default node types description
# Created: 12/01/2008
# Copyright (c) 2008,2009 by University College London
# Authors:
# Didrik Pinte <dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.

import logging

from sqlalchemy import and_

from hydroplatform.model import database as db
from hydroplatform.model import network as net
from hydroplatform.model import fields


DEFAULT_GROUP_NAME = u"Default"

FIELD_TIME_SERIE = u"time_serie"
FIELD_PARAMETER  = u"parameter"
FIELD_TABLE  = u"table"


SHP_CIRCLE    = u"circle"
SHP_ELLIPSE   = u"ellipse"
SHP_RECTANGLE = u"rectangle"
SHP_SQUARE    = u"square"
SHP_TRIANGLE  = u"triangle"
DEFAULT_SHAPE = u"circle"
SHAPES = [SHP_CIRCLE, SHP_ELLIPSE, SHP_SQUARE, SHP_RECTANGLE, SHP_TRIANGLE]

GENERAL_NODE = u"Default node"
DEMAND_NODE = u"Demand node"
STORAGE_NODE = u"Storage node"
DEFAULT_LINK = u"Default link"

GENERAL_NODE_DESCRIPTION = u''' '''
DEMAND_NODE_DESCRIPTION = u'''Represents consumptive use of water
Default data fields:
 - Fraction of consumptive use parameter
'''
STORAGE_NODE_DESCRIPTION = u''' Nodes that can store water over time (e.g. reservoirs, aquifers)
Default data fields:
 - Min and max volume capacity parameters
 - Min and max release
 - Initial storage
 - Boundary condition time-series
'''
#GENERAL_FIELDS = {u'boundary_condition' : (fields.TimeSerie, u"m3/s", u'Boundary conditions')}
DEMAND_FIELDS  = {u'consumptive_fraction' : (fields.Parameter, u"%", u'Parameters')}
STORAGE_FIELDS = {u'min_capacity' : (fields.Parameter, u"hm3", u'Capacity'),
                  u'max_capacity' : (fields.Parameter, u"hm3", u'Capacity'),
                  u'min_release' : (fields.Parameter, u"m3/s", u'Capacity'),
                  u'max_release' : (fields.Parameter, u"m3/s", u'Capacity'),
                  u'initial_storage' : (fields.Parameter, u"hm3", u'Capacity'),
                  u'boundary_condition' : (fields.TimeSerie, u"m3/s", u'Boundary conditions')}


DEMAND_SHAPE  = SHP_CIRCLE
DEMAND_PEN    = u"black"
DEMAND_BRUSH  = u"white"
DEMAND_SIZE   = 20 
STORAGE_SHAPE = SHP_TRIANGLE
STORAGE_PEN   = u"black"
STORAGE_BRUSH = u"#fdf982"
STORAGE_SIZE  =  20
GENERAL_SHAPE = SHP_CIRCLE
GENERAL_PEN   = u"white"
GENERAL_BRUSH = u"black"
GENERAL_SIZE  = 10


# TEMPORARY DEFINITIONS FOR JULIEN
AGRI_NODE = u"Agricultural node"
AGRI_DESC = u""
AGRI_SHAPE = SHP_ELLIPSE
AGRI_PEN  = u"#00ff5a"
AGRI_BRUSH  = u"#00ff5a"
AGRI_SIZE = 20
URB_NODE = u"Urban node"
URB_DESC = u""
URB_SHAPE = SHP_ELLIPSE
URB_PEN  = u"#ffbe32"
URB_BRUSH  = u"#ffbe32"
URB_SIZE = 20
GW_NODE = u"Groundwater node"
GW_DESC = u""
GW_SHAPE = SHP_SQUARE
GW_PEN = u"#00c6ff"
GW_BRUSH  = u"#00c6ff"
GW_SIZE = 20

default_types = {GENERAL_NODE : (GENERAL_NODE_DESCRIPTION, {}, 
                                 GENERAL_SHAPE,  GENERAL_PEN, GENERAL_BRUSH,
                                 GENERAL_SIZE), 
                 }

sample_types = {DEMAND_NODE : (DEMAND_NODE_DESCRIPTION, DEMAND_FIELDS, 
                                DEMAND_SHAPE, DEMAND_PEN, DEMAND_BRUSH, 
                                DEMAND_SIZE), 
                 STORAGE_NODE : (STORAGE_NODE_DESCRIPTION, STORAGE_FIELDS, 
                                 STORAGE_SHAPE,  STORAGE_PEN, STORAGE_BRUSH,
                                 STORAGE_SIZE),
                 AGRI_NODE : (AGRI_DESC, None, AGRI_SHAPE, AGRI_PEN, AGRI_BRUSH, AGRI_SIZE),
                 URB_NODE : (URB_DESC, None, URB_SHAPE, URB_PEN, URB_BRUSH, URB_SIZE),
                 GW_NODE : (GW_DESC, None, GW_SHAPE, GW_PEN, GW_BRUSH, GW_SIZE),
                 }


def register_sample_type(session, project):
    # all the nodes and fields types must use at least one group that is the default group
    defgrptype = session.query(db.FieldGroup).filter_by(name=u"Default").first()    
    if defgrptype is None:
        defgrptype = net.FieldGroup(u"Default") 
        session.add(defgrptype)     
        session.commit() 
    register_types(session, project, sample_types, defgrptype)

def register_default_type(session, project):
    # all the nodes and fields types must use at least one group that is the default group
    defgrptype = session.query(db.FieldGroup).filter_by(name=u"Default").first()    
    if defgrptype is None:
        defgrptype = net.FieldGroup(u"Default") 
        session.add(defgrptype)     
        session.commit()
    
    # search for an existing Default linktype object in db
    deflnktype = session.query(db.LinkType).filter(
                    and_(db.LinkType.project_id == project.id, 
                        db.LinkType.name == DEFAULT_LINK)
                    ).first()
    # register default link type if needed
    if deflnktype is None:
        ltype = db.LinkType(DEFAULT_LINK, 
                            u"Default link without fields", 
                            project) 
        ltype.size = 1
        ltype.pen = u"black"
        session.add(ltype)    
        logging.info("Default link type registered")
        # register the default group to this link type
        ltype.groups.append(defgrptype)
        session.commit()

    register_types(session, project, default_types, defgrptype)
    
def register_types(session, project, type_dict, defaultgrouptype):
    # register default node types
    for name, (description, dfields, shape, pen, brush, size) in type_dict.items():
        # check node type does not exist in db
        ntype = session.query(db.NodeType).filter(db.NodeType.project_id == project.id).filter_by(name=name).first()
        if ntype is None:
            
            # create nodetype
            ntype = net.NodeType(name, description, project, shape, pen, brush, size)
            # save it in db
            session.add(ntype)
            
            # register the default group to this object type
            ntype.groups.append(defaultgrouptype) 
            session.commit()
            
            if dfields is not None:
                for (field_name, (field_type, field_units, field_group)) in dfields.items():
                    # check group exists in db
                    ngroup = session.query(db.FieldGroup).filter_by(name=field_group).first()
                    if ngroup is None:
                        ngroup = db.FieldGroup(field_group)
                        session.add(ngroup)                        
                        session.commit()
                    try:
                        ntype.groups.index(ngroup)
                    except ValueError:
                        ntype.groups.append(ngroup)
                    # add it's node attributes
                    if field_type is fields.Parameter:
                        session.add(db.NodeField(ntype.id, field_type(name = field_name, 
                                                                      unit = field_units), field_name, ngroup))
                    elif field_type is fields.TimeSerie:
                        session.add(db.NodeField(ntype.id, field_type(name  = field_name, 
                                                                      step  = project.time_step,
                                                                      fromd = project.horizon_from,
                                                                      tod   = project.horizon_to,
                                                                      unit  = field_units), 
                                                 field_name, ngroup))
                    elif field_type is fields.Table:
                        pass
            logging.info('Registered %s' % ntype)
        else :
            logging.info("Type %s already registered" % ntype.name)
    session.commit()


def register_definition(session, project, definition):
    """
    FIXME : add some error management if definition is not correct
    """
    default_group = session.query(db.FieldGroup).filter_by(\
        name=u"Default").first()
    if default_group is None:
        default_group = db.FieldGroup(DEFAULT_GROUP_NAME)
        session.add(default_group)
        session.flush()
        print "Default group created"
    else:
        print "Default group already existing"
    
    keys, nts = definition.get_node_types(project)
    keys, lts = definition.get_link_types(project)
    
    for v in nts:
        if v[0] == u"Default node":
            # update description
            nt = session.query(db.NodeType).filter(
                and_(db.NodeType.name==v[0], 
                     db.NodeType.project_id == project.id)).first()
            if nt is not None:
                nt.description = v[1]            
            else:
                nt = db.NodeType(v[0], v[1], project, *v[2:-1])
        else:
            # check if node with same name already exists
            res = session.query(db.NodeType).filter(
                and_(db.NodeType.name==v[0],
                     db.NodeType.project_id==project.id)).first()
            if res is not None:
                raise Exception("Node type %s do already exist in project" % v[0])
            nt = db.NodeType(v[0], v[1], project, *v[2:-1])
        session.add(nt)            
        session.flush()
        for field in v[-1]:
            if isinstance(field, list):
                field, group = field
                # check if group exists
                grp = session.query(db.FieldGroup).filter_by(name=group).first()
                if grp is None:
                    grp = db.FieldGroup(group)
                    session.add(grp)
                    session.flush()
            else:
                grp = default_group
            nf = session.query(db.NodeField).filter(and_(db.NodeField.name == field.name,
                                                    db.NodeField.node_type_id == nt.id)).first()
            if nf is not None:
                raise Exception("Field %s for node type %s already exist in db" % (field.name, nt.name))            
            else:
                nf = db.NodeField(nt.id, field, field.name, grp)
            session.add(nf)
    session.commit()
            
    for v in lts : 
        if v[0] == "Default link":
            # update description
            nt = session.query(db.LinkType).filter_by(name=v[0]).first()
            if nt is not None:
                nt.description = v[1]            
            else:
                nt = db.LinkType(v[0], v[1], project, *v[2:-1])
        else:
            # check if node with same name already exists
            res = session.query(db.LinkType).filter(
                and_(db.LinkType.name==v[0],
                     db.LinkType.project_id==project.id)).first()
            if res is not None:
                raise Exception("Link type %s do already exist in project" % v[0])
        if len(v) > 3:
            lt = db.LinkType(v[0], v[1], project, *v[2:-1])
        elif len(v) == 3:
            lt = db.LinkType(v[0], v[1], project)
        else:
            lt = db.LinkType(v[0], u"", project)
            
        session.add(lt)            
        session.flush()
        
        for field in v[-1]:
            if isinstance(field, list):
                field, group = field
                # check if group exists
                grp = session.query(db.FieldGroup).filter_by(name=group).first()
                if grp is None:
                    grp = db.FieldGroup(group)
                    session.add(grp)
                    session.flush()
            else:
                grp = default_group
            lf = session.query(db.LinkField).filter(and_(db.LinkField.name == field.name,
                                                    db.LinkField.link_type_id == lt.id)).first()
            if lf is not None:
                raise Exception("Field %s for link type %s already exist in db" % (field.name, lt.name)) 
            lf = db.LinkField(lt.id, field, field.name, grp)
            session.add(lf)
    session.commit()
    